﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;

namespace BlackStar
{
    public class MoveableComponentContentReader : ContentTypeReader<MoveableComponent>
    {
        protected override MoveableComponent Read(ContentReader input, MoveableComponent existingInstance)
        {
            MoveableComponent result = new MoveableComponent();
            result.ReadContent(input);
            return result;
        }
    }

    public class MoveableComponent : Component
    {
        struct OverHandle
        {
            public bool Top;
            public bool Left;
            public bool Bottom;
            public bool Right;

            public bool Any() { return Top || Left || Bottom || Right; }
        }

        OverHandle myHandles;
        OverHandle startHandles;

        bool resizing = false;
        bool dragging = false;

        protected bool mySizeable = true;
        protected bool myDraggable = true;
        protected Rectangle myDragHandle = Rectangle.Empty;
        protected Rectangle myScreenDragHandle = Rectangle.Empty;
        protected DockStyle myDragHandleDock = DockStyle.None;

        int myHandleSize = 5;

        Point startResize = Point.Zero;
        Point startDrag = Point.Zero;

         
        protected Point myMinimumSize = new Point(15, 15);

        /// <summary>
        /// Turns dragging on and off for the component.
        /// </summary>
        [ContentSerializer(Optional = true)]
        public Rectangle DragHandle
        {
            get { return myDragHandle; }
            set { myDragHandle = value; }
        }

        [ContentSerializer(Optional = true)]
        public DockStyle DragHandleDock
        {
            get { return myDragHandleDock; }
            set
            {
                myDragHandleDock = value;
                // update the screen draw handle.
                UpdateRectangles();
            }
        }
        /// <summary>
        /// Turns dragging on and off for the component.
        /// </summary>
        [ContentSerializer(Optional = true)]
        public bool Draggable
        {
            get { return myDraggable; }
            set { myDraggable = value; }
        }

        // determines how far from the edge of the component
        // can be clicked to to resize it.
        [ContentSerializer(Optional = true)]
        public int HandleSize
        {
            get { return myHandleSize; }
            set { myHandleSize = value; }
        }

        [ContentSerializer(Optional = true)]
        public virtual Point MinimumSize
        {
            get { return myMinimumSize; }
            set { myMinimumSize = value; }
        }

        /// <summary>
        /// Turns sizing on and off for the component.
        /// </summary>
        [ContentSerializer(Optional = true)]
        public bool Sizeable
        {
            get { return mySizeable; }
            set { mySizeable = value; }
        }

        protected internal override void ReadContent(ContentReader input)
        {
            base.ReadContent(input);

            mySizeable = input.ReadBoolean();
            myHandleSize = input.ReadInt32();
            myDraggable = input.ReadBoolean();
            myDragHandle = input.ReadObject<Rectangle>();
            myDragHandleDock = (DockStyle)input.ReadInt32();
            myMinimumSize = input.ReadObject<Point>();
        }

        public override void DoMouseDown(MouseEventArgs e)
        {
            if (!myVisible)
            { base.DoMouseDown(e); }
            else
            {
                if (mySizeable && e.Buttons.Left)
                {
                    myHandles = GetOverHandle(e.Location);
                    if (myHandles.Any()) StartResize();
                }

                if (myDraggable && e.Buttons.Left)
                {
                    if (myScreenDragHandle.Contains(gui.mouse.Location)) StartDrag();
                }
            }
        }

        public override void Update(GameTime gameTime)
        {
            if (!myGui.mouse.MouseInViewport()) return;

            myHandles = GetOverHandle(myGui.mouse.Location);
            UpdateCursor(myHandles);

            // we have to do the resize and drag code in update
            // because if the cursor moves out of the form, then the form will stop
            // recieving move events.

            if (mySizeable && resizing)
            {
                DoResize();
                if (!myGui.mouse.LeftButton) resizing = false;
            }

            if (myDraggable && dragging)
            {
                DoDrag();
                if (!myGui.mouse.LeftButton) dragging = false;
            }


            base.Update(gameTime);
        }

        public override void DoMouseUp(MouseEventArgs e)
        {
            if (!myVisible)
            { base.DoMouseUp(e); } // pass to parent.
            else
            {
                resizing = false;
                dragging = false;
            }
        }

        protected void DoDrag()
        {
            myRectangle.X += gui.mouse.Location.X - startDrag.X;
            myRectangle.Y += gui.mouse.Location.Y - startDrag.Y;

            startDrag = gui.mouse.Location;

            // we aren't acutally resizing, but this will
            // cause the rectangles used to draw this component to update.
            this.Resize();
        }

        protected virtual Point GetMinimumSize()
        {
            // the base sizeable component does not have a theme object to
            // restrict it's size so we only return its own minimum size.
            // descendands of this component can override this to
            // include limitations of size based on theme.
            return myMinimumSize;
        }

        protected void DoResize()
        {
            int diff;
            bool lockY = false;
            bool lockX = false;

            Point minSize = GetMinimumSize();

            if (startHandles.Top)
            {
                diff = gui.mouse.Location.Y - startResize.Y;

                if (this.myRectangle.Height - diff >= minSize.Y)
                {
                    this.myRectangle.Y += diff;
                    this.myRectangle.Height -= diff;
                }
                else { lockY = true; }
            }
            if (startHandles.Bottom)
            {
                if (this.myRectangle.Height + (gui.mouse.Location.Y - startResize.Y) >= minSize.Y)
                { this.myRectangle.Height += (gui.mouse.Location.Y - startResize.Y); }
                else
                { lockY = true; }
            }
            if (startHandles.Left)
            {
                diff = gui.mouse.Location.X - startResize.X;
                if (this.myRectangle.Width - diff >= minSize.X)
                {
                    this.myRectangle.X += diff;
                    this.myRectangle.Width -= diff;
                }
                else
                { lockX = true; }
            }
            if (startHandles.Right)
            {
                if (this.myRectangle.Width + (gui.mouse.Location.X - startResize.X) >= minSize.X)
                { this.myRectangle.Width += (gui.mouse.Location.X - startResize.X); }
                else
                { lockX = true; }
            }

            startResize = new Point(lockX ? startResize.X : gui.mouse.Location.X,
                lockY ? startResize.Y : gui.mouse.Location.Y);

            // we resize the parent so that it can update it dock space if this this control
            // is docked. The parent will propagate the resize back down into this.
            Parent.Resize();
        }

        protected void StartResize()
        {
            resizing = true;
            startResize = gui.mouse.Location;
            startHandles = GetOverHandle(startResize);
        }

        protected void StartDrag()
        {
            dragging = true;
            startDrag = gui.mouse.Location;
        }

        OverHandle GetOverHandle(Point p)
        {
            OverHandle handles;
            bool overMe = this.IsMouseOver(p);

            if (overMe)
            {
                handles.Left = (p.X <= this.myDrawRectangle.X + myHandleSize);
                handles.Top = (p.Y <= this.myDrawRectangle.Y + myHandleSize);

                handles.Right = (p.X >= this.myDrawRectangle.X + this.myDrawRectangle.Width - myHandleSize);
                handles.Bottom = (p.Y >= this.myDrawRectangle.Y + this.myDrawRectangle.Height - myHandleSize);
            }
            else
            { handles.Left = false; handles.Right = false; handles.Top = false; handles.Bottom = false; }
            return handles;
        }

        void UpdateCursor(OverHandle handles)
        {
            if (mySizeable)
            {

                // calls to change the cursor will silently fail
                // if the cursor names are not found in the current theme.
                if ((handles.Top && handles.Left) || (handles.Bottom && handles.Right))
                { this.Cursor = "NWSE"; }
                else if ((handles.Top && handles.Right) || (handles.Bottom && handles.Left))
                { this.Cursor = "NESW"; }
                else if (handles.Top || handles.Bottom)
                { this.Cursor = "NS"; }
                else if (handles.Left || handles.Right)
                { this.Cursor = "EW"; }
                else
                { this.Cursor = "Default"; }
            }
            else
            { this.Cursor = "Default"; }

            if (myDraggable && myScreenDragHandle.Contains(gui.mouse.Location) && IsMouseOver(gui.mouse.Location))
            {
                this.Cursor = "Move";
            }
        }

        public override void Resize()
        {
            if (layoutSuspended) return;
            UpdateRectangles();


            switch (myDragHandleDock)
            {
                case DockStyle.Top:
                    myScreenDragHandle.X = myDrawRectangle.X + myHandleSize;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myHandleSize;
                    myScreenDragHandle.Width = myDrawRectangle.Width - (myHandleSize * 2);
                    myScreenDragHandle.Height = myDragHandle.Height;
                    break;
                case DockStyle.Bottom:
                    myScreenDragHandle.X = myDrawRectangle.X + myHandleSize;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myDrawRectangle.Height - myDragHandle.Height - myHandleSize;
                    myScreenDragHandle.Width = myDrawRectangle.Width - (myHandleSize * 2);
                    myScreenDragHandle.Height = myDragHandle.Height;
                    break;
                case DockStyle.Left:
                    myScreenDragHandle.X = myDrawRectangle.X + myHandleSize;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myHandleSize;
                    myScreenDragHandle.Width = myDrawRectangle.Width;
                    myScreenDragHandle.Height = myDragHandle.Height - (myHandleSize * 2);
                    break;
                case DockStyle.Right:
                    myScreenDragHandle.X = myDrawRectangle.X + myDrawRectangle.Width - myDragHandle.Width - myHandleSize;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myHandleSize;
                    myScreenDragHandle.Width = myDragHandle.Width;
                    myScreenDragHandle.Height = myDrawRectangle.Height - (myHandleSize * 2);
                    break;
                case DockStyle.Fill:
                    myScreenDragHandle.X = myDrawRectangle.X + myHandleSize;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myHandleSize;
                    myScreenDragHandle.Width = myDrawRectangle.Width - (myHandleSize * 2);
                    myScreenDragHandle.Height = myDrawRectangle.Height - (myHandleSize * 2);
                    break;
                default:
                    myScreenDragHandle.X = myDrawRectangle.X + myDragHandle.X;
                    myScreenDragHandle.Y = myDrawRectangle.Y + myDragHandle.Y;
                    myScreenDragHandle.Width = myDragHandle.Width;
                    myScreenDragHandle.Height = myDragHandle.Height;
                    break;
            }

            foreach (Component c in Children) { c.Resize(); }
            OnResize();
        }
    }
}
